#include <iostream>
#include <string>
#include <sys/select.h>
#include "Sock.hpp"

#define NUM ((sizeof(fd_set))*8) //最大的位数 <==>最多可以等待的文件描述符个数
int fd_array[NUM]; //数组内容fd_array[i] >=0,认为是合法的fd,其值就是有效的文件描述符,如果是-1,该位置无效

static void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}
//之后我们是这样启动程序的: ./select_server port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port =(uint16_t)atoi(argv[1]);
    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock, port);//套接字和端口号绑定
    Sock::Listen(listen_sock);//监听

    //先将数组中的所有位置设置为无效
    for(int i = 0;i<NUM;i++)
    {
        fd_array[i] = -1;
    }
    fd_array[0] = listen_sock;//将监听套接字添加到fd_array数组中的第0个位置

    
    fd_set readfds;//读文件描述符集
    //fd_array是用来保存具体的文件描述符  rreadfds是为了记录当前我们关心哪些文件描述符的读取就绪
    //事件循环
    for(;;)
    {
        FD_ZERO(&readfds);//每一次都要对位图重新设置,先清空
        int max_fd = fd_array[0];//找当前最大的文件描述符
        for (int i = 0; i < NUM; i++)
        {
            if (fd_array[i] == -1) //这个位置不是合法的文件描述符
                continue;

            //当前是合法的fd
            FD_SET(fd_array[i], &readfds); //将有效位置的文件描述符添加到readfds,当前文件描述符的读取要被关注
            if (max_fd < fd_array[i])
            {
                max_fd = fd_array[i]; //更新最大fd
            }
        }
        
        // 我们的服务器上的所有的fd(包括listen_sock),都要交给select进行检测！！ select一次可以等待多个文件描述符
        // recv,read,write,send,accept : 只负责自己最核心的工作:即真正的读写
        //对于listen_sock就是真正的accept,把链接从底层获取上来
        int n = select(max_fd + 1, &readfds, nullptr, nullptr, nullptr); //timeout为空:阻塞等待

        switch(n)
        {
            case -1:    //select调用失败
                std::cerr << "select error" << std::endl;
                break;
            case 0://超时  超时不算错误！
                std::cout << "select timeout" << std::endl;
                break;
            default://有文件描述符就绪了
                std::cout << "有fd对应的事件就绪啦!" << std::endl;
                //由于我们不知道是哪一个文件描述符对应的事件就绪了
                //所以需要遍历fd_array数组依次判断各个文件描述符对应的读事件是否就绪
                for(int i = 0;i<NUM;i++)
                {
                    if(fd_array[i] == -1)//跳过无效的位置
                        continue;

                    //来到这,当前的fd都是有效的fd,但是合法的fd不一定是就绪的fd   
                    //所以需要根据输入输出型参数readfds当中确定哪一个有效的fd就绪了
                    //(当前的这个readfds位图是内核返回给用户的:你所关心的文件描述符就绪的情况)
                    //FD_ISSET:检测一个文件描述符是否在该集合里被设置
                    //检测当前fd_array[i]对应的fd是否在readfds被设置,如果被设置,那说明该文件描述符读取事件就绪了
                    if(FD_ISSET(fd_array[i], &readfds))
                    {
                        std::cout << "sock: " << fd_array[i] << " 上面有了读事件，可以读取了" << std::endl;
                        //当前fd_array[i]的读取事件就绪了
                        if (fd_array[i] == listen_sock) //如果此时是监听套接字读取就绪了->此时应该accpet
                        {
                            std::cout << "listen_sock: " << listen_sock << " 有了新的链接到来" << std::endl;
                            int sock = Sock::Accept(listen_sock);
                            if (sock >= 0) //获取新连接成功
                            {
                                std::cout << "listen_sock: " << listen_sock << " 获取新的链接成功" << std::endl;
                                //获取链接成功是否可以recv/read了呢？绝对不能！
                                //新链接到来，不意味着有数据到来！！什么时候数据到来呢?不知道
                                //如果数据没到,而我们进行了recv就得阻塞等待挂起了
                                ///可是谁可以最清楚的知道那些fd读取是否就绪了呢？select！
                                //但是现在无法直接将fd设置进select，但是，好在我们有fd_array[]!
                                //我们可以把当前的文件描述符设置到fd_array[]
                                //下一轮循环就会把这个fd放到位图中,下次就可以检测这个文件描述符读取是否就绪

                                //找fd_array数组没有被使用的位置
                                int pos = 1;
                                for (; pos < NUM; pos++)
                                {
                                    if (fd_array[pos] == -1)
                                        break;
                                }
                                
                                // case1. 找到了一个位置没有被使用,把当前文件描述符放到pos位置
                                if (pos < NUM)
                                {
                                    std::cout << "新链接: " << sock << " 已经被添加到了数组[" << pos << "]的位置" << std::endl;
                                    fd_array[pos] = sock;
                                }
                                else// case2. 找完了所有的fd_array[],都没有找到没有被使用位置
                                {
                                    // 说明服务器已经满载，没法处理新的请求了
                                    std::cout << "服务器已经满载了，关闭新的链接" << std::endl;
                                    close(sock);
                                }
                            }
                        }
                        else    // 普通的文件描述符fd_array[i]的读事件就绪啦！
                        {
                            // 此时可以进行读取啦，recv/read
                            // 可是，本次读取就一定能读完吗？不一定,因为TCP存在流量控制,拥塞控制等处理手段
                            //假设读完也可能存在数据包粘包问题,例如:读取到了1.5个数据包
                            // 但是,我们今天没法解决！我们今天没有场景！仅仅用来测试
                            std::cout << "sock: " << fd_array[i] << " 上面有普通读取" << std::endl;
                            char recv_buffer[1024] = {0};
                            //此时读取不会阻塞,因为读取事件已经就绪了
                            ssize_t s = recv(fd_array[i], recv_buffer, sizeof(recv_buffer) - 1, 0);
                            if (s > 0)
                            {
                                recv_buffer[s] = '\0';
                                std::cout << "client[ " << fd_array[i] << "]# " << recv_buffer << std::endl;
                            }
                            else if (s == 0)    //对端关闭了链接
                            {
                                std::cout << "sock: " << fd_array[i] << "关闭了, client退出啦!" << std::endl;
                                //对端关闭了链接,我们也要把这个链接关掉
                                close(fd_array[i]);//关闭链接

                                //在数组中去掉这个文件描述符
                                std::cout << "已经在数组下标fd_array[" << i << "]"<< "中,去掉了sock: " << fd_array[i] << std::endl;
                                fd_array[i] = -1;
                            }
                            else    //读取失败
                            {                        
                                close(fd_array[i]);
                                std::cout << "已经在数组下标fd_array[" << i << "]"<< "中,去掉了sock: " << fd_array[i] << std::endl;
                                fd_array[i] = -1;
                            }
                        }
                    }
                }
            break;
        }
    }
    return 0;
}
